/*!
 * @file
 * @brief auto support portmap in routers by upnp protocal
 * @date  2013-8-13
 * @author suijz
 *
 * support any port to map ruters,and back real extern port
 */

#include "upnp.h"

#include "miniupnpc.h"
#include "upnpcommands.h"
#include "upnperrors.h"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <string> 
#include <map>

using namespace std;

const Port s_minPort = 1000;///<extern min port 
const Port s_maxPort = 1500;///<extern max port
const char s_proto[2][4]= {"TCP","UDP"};

const int IP_LENGTH = 40;
const int PORT_LENGTH = 10;

struct UPnPPrivate
{
    UPnPPrivate():bValid(false),pDevList(NULL){};
    ~UPnPPrivate()
    {
        reset();
    }
    void reset()
    {
        FreeUPNPUrls(&urls);
        if (pDevList)
        {
            freeUPNPDevlist(pDevList);
            pDevList = NULL;
        }
        memset(szLanIP,0,sizeof(szLanIP));
        memset(szOutIP,0,sizeof(szOutIP));
        bValid = false;
    }

    bool bValid;
    char szLanIP[IP_LENGTH];
    char szOutIP[IP_LENGTH];
    string strLastIP;///<shuld not reset
    UPNPDev* pDevList;
    UPNPUrls urls;
    IGDdatas data;
};

static struct UPnPPrivate s_UPnPDevice;
typedef std::map<Port,Port> InExtPortMap;

///< inner port and extern port map
static InExtPortMap s_portMap;

UPnP::UPnP()
{
}

UPnP::~UPnP()
{
    unregAllPort();
}

UPnP& UPnP::instance()
{
    static UPnP sUPnP;
    return sUPnP;
}

int UPnP::regMapPort(Port iport,Port& rport,const char* desc,int proto)
{
    int r = 0;
    Port eport = s_portMap[iport];
    rport = 0;
    if (0==eport)//first,use iport 
        eport = iport;

    r = addPort(rport,eport,iport,0,desc,proto,0);
    if (r!=0)
        return r;

    s_portMap[iport] = rport;//save for next reg,avoid search rang [s_minport,s_naxport]

    return r;
}

int UPnP::unregMapPort(Port eport,int proto)
{
    return delPort(eport,proto);
}
void UPnP::unregAllPort()
{
    InExtPortMap::iterator it = s_portMap.begin();
    for (;it!=s_portMap.end();it++)
    {
        unregMapPort(it->second);
    }
    s_portMap.clear();
}
/*!
@brief

@return <0 err; 0 :no igd 1 found igd,2 found but not connect ,2 not igd
*/
int UPnP::search(int delay)
{
    int r = 0;

    s_UPnPDevice.reset();

    s_UPnPDevice.pDevList = upnpDiscover(delay,0,0,0,0,&r);
    if (!s_UPnPDevice.pDevList)
    {
        printf("search nofound UPnP Device err=%d!\n",r);
        return r;
    }
   
    int n = UPNP_GetValidIGD(s_UPnPDevice.pDevList,&s_UPnPDevice.urls,&s_UPnPDevice.data,
				s_UPnPDevice.szLanIP,sizeof(s_UPnPDevice.szLanIP));
    printf("UPNP_GetValidIGD n =%d %s!\n",n,s_UPnPDevice.urls.controlURL);
    if (n != 1)
    {
        return -3;
    }

    r = UPNP_GetExternalIPAddress(s_UPnPDevice.urls.controlURL,
			s_UPnPDevice.data.first.servicetype,s_UPnPDevice.szOutIP);
    if (r != UPNPCOMMAND_SUCCESS)
    {
        printf("GetExternalIPAddress failed error=%d!\n",r);
        return -4;
    }
    if (!s_UPnPDevice.strLastIP.empty() && 0!=strcmp(s_UPnPDevice.szLanIP,s_UPnPDevice.strLastIP.c_str()))
    {
        printf("ip change %s!=%s,s_UPnPDevice.szLanIP,s_UPnPDevice.strLastIP.c_str()\n");
        unregAllPort();
    }
    s_UPnPDevice.strLastIP = s_UPnPDevice.szLanIP;
    //printf("LocalIP=%s ExternalIP = %s!\n",s_UPnPDevice.szLanIP,s_UPnPDevice.szOutIP);

    return n;//return cont of IGD 
}
/*!
@brief  map local port into ruters for get extern port
@return 0:ok !=0 error
*/
int UPnP::addPort(Port& rport,Port eport, Port iport,
		const char* iaddr,const char* desc,int proto,int duration)
{
    //1.find eport and proto used by other device(iaddr+iport)
    //2.if not find ,use this eport and proto,and then map it
    //3.if find,use other eport and repeat step 1,go on
    int r = 0;
    char localIP[IP_LENGTH]={0};
    char localPort[PORT_LENGTH]={0};
    int  nLocalPort = 0;
    char szeport[PORT_LENGTH]={0};
    char sziport[PORT_LENGTH]={0};
    char szduration[20]={0};

    sprintf(szeport,"%d",eport);
    sprintf(sziport,"%d",iport);
    sprintf(szduration,"%d",duration);
    std::string seport = szeport;//std::to_string(static_cast<int>(eport));
    std::string siport = sziport;//std::to_string(iport);
    std::string sdura  = szduration;
    std::string sproto = s_proto[proto];

    if (!iaddr)
         iaddr = s_UPnPDevice.szLanIP;
    
    r = findPort(seport.c_str(),sproto.c_str(),localIP,localPort);
    if (0!=r)//not used,will map it
    {
        rport = eport;
        return addPort(seport.c_str(),siport.c_str(),iaddr,desc,sproto.c_str(),sdura.c_str());
    }

    //std::string sLocalPort = localPort;
    nLocalPort = atoi(localPort);
    if (!strcmp(iaddr,localIP) && (nLocalPort==iport))
    {
        rport = eport;
        return 0;//exits,already map,also ok
    }

    //eport and proto used by other
    memset(localIP,0,sizeof(localIP));
    memset(localPort,0,sizeof(localPort));

    for (int i=s_minPort;i<s_maxPort;i++)
    {
        sprintf(szeport,"%d",i);
        seport = szeport;//std::to_string(i);
        r = findPort(seport.c_str(),sproto.c_str(),localIP,localPort);
        if (0!=r)//not used,will map it
        {
            rport = i;            
            return addPort(seport.c_str(),siport.c_str(),iaddr,desc,sproto.c_str(),sdura.c_str());
        }
        nLocalPort = atoi(localPort);
        if (!strcmp(iaddr,localIP) && (nLocalPort==iport))
        {
            rport = i;            
            return 0;//exits,already map,also ok
        }
    }

    return r;
}

int UPnP::addPort(const char* eport,const char* iport,const char* iaddr,
		const char* desc,const char* proto,const char* duration)
{
    int r = 0;
 
    r = UPNP_AddPortMapping(s_UPnPDevice.urls.controlURL, s_UPnPDevice.data.first.servicetype,
		eport,iport,iaddr,desc,proto,0,duration);

    if(r!=UPNPCOMMAND_SUCCESS)
    {
        printf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n",
	          eport, iport, iaddr, r, strupnperror(r));

        return r;
    }
    printf("NewMap external %s:%s %s=>%s:%s (expire=%s)\n",
	       s_UPnPDevice.szOutIP, eport, proto, iaddr, iport, duration);

    return r;
}

int UPnP::delPort(Port eport,int proto)
{
    char szeport[PORT_LENGTH]={0};

    sprintf(szeport,"%d",eport);
    std::string seport = szeport;//std::to_string(static_cast<int>(eport));
    std::string sproto = s_proto[proto];

    return delPort(seport.c_str(),sproto.c_str());
}

int UPnP::delPort(const char* eport,const char* proto)
{
    int r = 0;

    r = UPNP_DeletePortMapping(s_UPnPDevice.urls.controlURL, 
		s_UPnPDevice.data.first.servicetype, eport, proto, 0);
    printf("UPNP_DeletePortMapping() port=%s proto=%s returned : %d\n", eport,proto,r);

    return r;
}

int UPnP::findPort(int eport,int proto,char* iaddr,Port& iport)
{

    return 0;
}
int UPnP::findPort(const char* eport,const char* proto,char* iaddr,char* iport)
{
    char desc[100]={0};
    char enable[10]={0};
    char duration[20]={0};
  //  char iport[10];
  //  char iaddr[20];
    int r = UPNP_GetSpecificPortMappingEntry(s_UPnPDevice.urls.controlURL,
	    s_UPnPDevice.data.first.servicetype,eport,proto,iaddr,iport,desc,enable,duration);

    if(r!=UPNPCOMMAND_SUCCESS)
	printf("NotMap=>%s:%s %s %d(%s)\n",s_UPnPDevice.szOutIP,eport,proto, r, strupnperror(r));

    if(iaddr[0]) 
    {
	//printf("InternalIP:Port = %s:%s\n", iaddr, iport);
	printf("ExistMap external %s:%s %s=>%s:%s %s %s (expire=%s)\n",
		       s_UPnPDevice.szOutIP, eport, proto, iaddr, iport, enable,desc,duration);
    }

    return r;
}
